
#region Imported Namespaces
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;
using System.Xml;

using SelectTree = PHSRAG.WebControls.SelectTree;
using DataAccess = PHSRAG.Utility.DataAccess;
using Policy = PHSRAG.Insight21.Policy;
using UserContext = PHSRAG.Insight21.Context.UserContext;
using PHSRAG.Insight21.Admin;


#endregion

namespace PHSRAG.Insight21.InsightWebControls
{
	/// <summary>
	/// The OrgTree offers a hierarchical representation of the org tree from which selection may be made.
	/// </summary>
	public class AdminOrgTree : SelectTree
	{
		#region Constants
		private const string PersonIdKey = "personIdKey_F940A82C-F599-4863-8243-A1351D9C7E5B";
		private const string IncomingDataKey = "incomingDataKey_06AB2FD7-D783-4d8e-AC5E-2119C71AF9A4";
		private const string OrgRightsKey = "orgRightsKey_F940A82C-F599-4863-8243-A1351D9C7E5B";
		#endregion

		#region Private Types
		/// <summary>
		/// The DataTableIndex enumeration defines table index within the datasource.
		/// </summary>
		private enum DataTableIndex
		{
			UserSpecific, All
		}

		/// <summary>
		/// The ElementIndex enmeration defines the nesting order of organizational elements.
		/// </summary>
		private enum ElementIndex
		{
			Domain, Institution, Department, Unit
		}
		#endregion

		#region Overrides
		/// <summary>
		/// Handler for the event raised when the control is being initialized.
		/// Retrieve the cache policy in force. 
		/// </summary>
		/// <param name="e">Argument associated with the event</param>
		protected override void OnInit(EventArgs e)
		{
			base.OnInit (e);
		}
		#endregion

		#region SelectTree Methods
		/// <summary>
		/// Generate the tree (both the data and the associated HTML).
		/// </summary>
		protected override void GenerateTree()
		{
			if (!Page.IsPostBack)
				GenerateOrgTreeHTML(GenerateOrgTree(), CurrentSelection);
		}

		/// <summary>
		/// Get the code for the client script block (if any) that will be registered.
		/// </summary>
		/// <returns>String representation of the script code</returns>
		protected override string GetClientScriptBlock()
		{
			return
				"<script language=\"javascript\">\n" +
//				"function ShouldTreeSelectionCascade(itemType)\n" +
//				"{{\n\treturn false;\n}}\n" +
				"function DoNothing()\n" +
				"{{\n\treturn;\n}}\n" +
				"</script>\n";
		}

		/// <summary>
		/// Get the code for the client startup script if any) that will be registered.
		/// </summary>
		/// <returns>String representation of the script code</returns>
		protected override string GetStartupScript()
		{
			return string.Format(
				"<script language=\"javascript\">\nExpandTree('{0}Root');\n</script>\n", ClientID);
		}

		/// <summary>
		/// Get the list of selections made in the tree. Each selection constitutes a pair of values; a key and a value.
		/// The key is of the form "chiefCode-orgName", and the value is the org ID. The selections are represented by
		/// a string of the form "chiefCode-orgName:orgID" where multiple selections are separated by '|'.
		/// </summary>
		public override string Selection
		{
			get
			{
				StringBuilder selection = new StringBuilder();
				//				Regex regex = new Regex(@"\w+_(?<name>[^:]+):(?<id>\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
				Regex regex = new Regex(@"\w+_(?<id>[^:]+):(?<name>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
				foreach (string key in Page.Request.Form.Keys)
				{
					Match match = regex.Match(key);
					if (match.Success)
						selection.AppendFormat("{0}{1}:{2}", (selection.Length == 0) ? "" : "|", 
							match.Groups["name"].Value, match.Groups["id"].Value);
				}
				return selection.ToString();
			}
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Add a new child to an existing XML node. The new child will be assigned the OrgID, BusinessUnit, ChiefCode
		/// attributes under the names "ID", "BU", "CC" respectively.
		/// </summary>
		/// <param name="attachmentTitle">XmlDocument on which the child is to be created</param>
		/// <param name="parentNode">Parent node of the new child node</param>
		/// <param name="id">Id of the org</param>
		/// <param name="triplet">Triplet containg the Org Name, BusinessUnit and ChiefCode for the child node</param>
		/// <returns>Newly created node</returns>
		private static XmlNode AddChild(XmlDocument doc, XmlNode parentNode, string id, Triplet triplet)
		{
			XmlNode childNode = doc.CreateElement(XmlConvert.EncodeLocalName(triplet.First.ToString()));

			XmlAttribute attribute = doc.CreateAttribute("ID");
			attribute.Value = id;
			childNode.Attributes.Append(attribute);

			attribute = doc.CreateAttribute("BU");
			attribute.Value = triplet.Second.ToString();
			childNode.Attributes.Append(attribute);

			attribute = doc.CreateAttribute("CC");
			attribute.Value = triplet.Third.ToString();
			childNode.Attributes.Append(attribute);

			parentNode.AppendChild(childNode);
			return childNode;
		}

		/// <summary>
		/// Generate the org tree based on the information stored in a data table. The tree is represented in XML.
		/// The tree is built based on the fully-qualified-name of each entry in the data table containing the organization
		/// information. This name takes the form "Domain > Institution > Department > Unit". The name is split into its
		/// constituents, and based on their position, added to the tree in the proper place.
		/// When a node is added to the tree, its name will take the form "ChiefCode-Name" if that entry does have a valid
		/// chief code. Otherwise, its name is just whatever it was in the fully-qualified-name.
		/// Each node is created with the OrgID, BusinessUnit, ChiefCode, FullyQualifiedName attributes,
		/// named "ID", "BU", "CC", "QN" respectively.
		/// The data source contains two tables; the first table contains the organization information specific to the
		/// user, and the second table contains the entire organiztion information (this table is used for lookups).
		/// </summary>
		/// <returns>XmlDocument which contains the hierarchical structure of the organization</returns>
		private XmlDocument GenerateOrgTree()
		{
			DataTable userOrgInfo = ((DataTable[])DataSource)[(int)DataTableIndex.UserSpecific];
			DataTable orgInfo = ((DataTable[])DataSource)[(int)DataTableIndex.All];
			XmlDocument doc = new XmlDocument();
			XmlNode rootNode = doc.CreateElement("OrgTree");
			doc.AppendChild(rootNode);

			foreach (DataRow row in userOrgInfo.Rows)
			{
				string domainId = Convert.ToString(row["DomainId"]); 
				Triplet triplet = GetNameBusinessUnitChiefCode(orgInfo, domainId);

				XmlNode domainNode = rootNode.SelectSingleNode(string.Format("*[@ID={0}]", domainId));
				if (domainNode == null)
					domainNode = AddChild(doc, rootNode, domainId, triplet);

				if ( row["InstitutionId"] != System.DBNull.Value)
				{
					string institutionId = Convert.ToString(row["InstitutionId"]);
					triplet = GetNameBusinessUnitChiefCode(orgInfo, institutionId);

					XmlNode institutionNode = domainNode.SelectSingleNode(string.Format("*[@ID={0}]", institutionId));
					if (institutionNode == null)
						institutionNode = AddChild(doc, domainNode, institutionId, triplet);

					if ( row["DepartmentId"] != System.DBNull.Value)
					{
						string departmentId = Convert.ToString(row["DepartmentId"]);
						triplet = GetNameBusinessUnitChiefCode(orgInfo, departmentId);

						XmlNode departmentNode = institutionNode.SelectSingleNode(string.Format("*[@ID={0}]", departmentId));
						if (departmentNode == null)
							departmentNode = AddChild(doc, institutionNode, departmentId, triplet);

						if ( row["UnitId"] != System.DBNull.Value)
						{
							string unitId = Convert.ToString(row["UnitId"]);
							triplet = GetNameBusinessUnitChiefCode(orgInfo, unitId);

							XmlNode unitNode = departmentNode.SelectSingleNode(string.Format("*[@ID={0}]", unitId));
							if (unitNode == null)
								AddChild(doc, departmentNode, unitId, triplet);
						}
					}				
				}			
			}

			System.Diagnostics.Trace.WriteLine(doc.InnerXml);
			return doc;
		}

		/// <summary>
		/// Generate the HTML representation of the org tree.
		/// Each node in the hiearchy will have a check-box. When the XML is created (by GenerateOrgTree), the name of each
		/// node would have been encoded (to allow names with embedded spaces, and special characters). Such names are
		/// decoded while generating HTML.
		/// The ID of each node is of the form "ControlClientID_OrgID".
		/// </summary>
		/// <param name="attachmentTitle">XmlDocument containing the org tree hierarchy</param>
		/// <param name="currentSelection"></param>
		private void GenerateOrgTreeHTML(XmlDocument doc, Hashtable currentSelection)
		{
			Controls.Add(new LiteralControl(
				string.Format("<div class=\"OrgTree\"><ul class=\"SelectTree\" id=\"{0}Root\">\n", ClientID)));

			foreach (XmlNode domainNode in doc.DocumentElement.ChildNodes)
			{
				Controls.Add(new LiteralControl(string.Format(
					"<li itemType=\"domain\">" +
					"<input type=\"checkbox\" id=\"{0}_{1}:{2}\" name=\"{0}_{1}:{2}\" {3} " +
					"onclick=\"DoNothing();\">&nbsp;{2}\n" +
					"<ul>\n",
					ClientID, domainNode.Attributes["ID"].Value, GetName(domainNode),
					IsSelected(currentSelection, domainNode))));

				foreach (XmlNode institutionNode in domainNode.ChildNodes)
				{
					Controls.Add(new LiteralControl(string.Format(
						"<li itemType=\"institution\">" +
						"<input type=\"checkbox\" id=\"{0}_{1}:{2}\" name=\"{0}_{1}:{2}\" {3} " +
						"onclick=\"DoNothing();\">&nbsp;{2}\n" +
						"<input type=\"checkbox\" id=\"{0}_{1}:{2}_AllChildren\" name=\"{0}_{1}:{2}_AllChildren\" {4} " +
						"onclick=\"DoNothing();\">&nbsp;All Children\n" +
						"<ul>\n",
						ClientID, institutionNode.Attributes["ID"].Value, GetName(institutionNode),
						IsSelected(currentSelection, institutionNode),
						IsRecursiveSelected(currentSelection, institutionNode))));

					foreach (XmlNode departmentNode in institutionNode.ChildNodes)
					{
						Controls.Add(new LiteralControl(string.Format(
							"<li itemType=\"department\">" +
							"<input type=\"checkbox\" id=\"{0}_{1}:{2}\" name=\"{0}_{1}:{2}\" {3} " +
							"onclick=\"DoNothing();\">&nbsp;{2}\n" +
							"<input type=\"checkbox\" id=\"{0}_{1}:{2}_AllChildren\" name=\"{0}_{1}:{2}_AllChildren\" {4} " +
							"onclick=\"DoNothing();\">&nbsp;All Children\n" +
							"<ul>\n",
							ClientID, departmentNode.Attributes["ID"].Value, GetName(departmentNode),
							IsSelected(currentSelection, departmentNode),
							IsRecursiveSelected(currentSelection, departmentNode))));

						foreach (XmlNode unitNode in departmentNode.ChildNodes)
						{
							Controls.Add(new LiteralControl(string.Format(
								"<li itemType=\"unit\">" +
								"<input type=\"checkbox\" id=\"{0}_{1}:{2}\" name=\"{0}_{1}:{2}\" {3} " +
								"onclick=\"DoNothing();\">&nbsp;{2}</li>\n",
								ClientID, unitNode.Attributes["ID"].Value, GetName(unitNode),
								IsSelected(currentSelection, unitNode))));
						}
						Controls.Add(new LiteralControl("</ul></li>\n"));
					}
					Controls.Add(new LiteralControl("</ul></li>\n"));
				}
				Controls.Add(new LiteralControl("</ul></li>\n"));
			}
			Controls.Add(new LiteralControl("</ul></div>\n"));
		}

		/// <summary>
		/// Get the ORgID, BusinessUnit and ChiefCode from a fully-qualified name of an organization entity. This is
		/// done via simple lookup in the DataTable which contains the information.
		/// </summary>
		/// <param name="orgInfo">DataTable containing the organization information</param>
		/// <param name="fullyQualifiedName">Fully-qualified name of the organization entity</param>
		/// <returns>A Trip which contains the ORgID, BusinessUnit and ChiefCode</returns>
		private static Triplet GetNameBusinessUnitChiefCode(DataTable orgInfo, string orgId)
		{
			DataRow[] matchingRows = orgInfo.Select(string.Format("Id={0}", orgId));
			if (matchingRows.Length != 1)
			{
				//! Temp code: If the data were correct, there would be no situation where we would hit this code block.
				// The data is ordered on the business unit and chief code thereby guaranteeing that assertion. However,
				// the data as of now is NOT correct. So, the hack is to set the OrgID, BusinessUnit and ChiefCode to
				// invalid but acceptable values.
				// instead of throwing an exception. When the data is fixed, THIS HACK MUST BE TAKEN OUT.

				return new Triplet(-1, string.Empty, string.Empty);

				//				throw new Exception(string.Format("FullQualifiedName {0} returned more than one match ({1})",
				//					fullyQualifiedName, matchingRows.Length));
			}
			return new Triplet(matchingRows[0]["OrganizationName"], matchingRows[0]["BusinessUnitNumber"], matchingRows[0]["ChiefCode"]);
		}

		/// <summary>
		/// Get the name to be used for an XmlNode. If the node has a non-empty chief-code attribute, then the name will
		/// be of the form "ChiefCode-Name". Else, it will be just the name of the node itself.
		/// </summary>
		/// <param name="node">XmlNode whose name is needed</param>
		/// <returns>Name of the node</returns>
		private static string GetName(XmlNode node)
		{
			string chiefCode = node.Attributes["CC"].Value;
			string nodeName = XmlConvert.DecodeName(node.Name);
			return (String.IsNullOrEmpty(chiefCode)) ? nodeName : string.Format("{0}-{1}", chiefCode, nodeName);
		}

		/// <summary>
		/// Return a string based on whether or not the specified value of the ID attribute
		/// of the xml element exists in the specified Hashtable.
		/// </summary>
		/// <param name="currentSelect">Hashtable to use for key lookup</param>
		/// <param name="node">xml element containing the ID attribute used as the key to look up.</param>
		/// <returns>The string "checked" if the key exists in the Hashtable; "" otherwise</returns>
		private static string IsSelected(Hashtable currentSelect, XmlNode node)
		{
			string key = node.Attributes["ID"].Value;
			return (currentSelect == null) ? "" : (currentSelect.ContainsKey(key) ? "checked" : "");
		}

		/// <summary>
		/// Return a string based on whether or not the specified value of the ID attribute
		/// of the xml element exists in the specified Hashtable.
		/// </summary>
		/// <param name="currentSelect">Hashtable to use for key lookup</param>
		/// <param name="node">xml element containing the ID attribute used as the key to look up.</param>
		/// <returns>The string "checked" if the key exists in the Hashtable; "" otherwise</returns>
		private static string IsRecursiveSelected(Hashtable currentSelect, XmlNode node)
		{
			StringBuilder sb = new StringBuilder(node.Attributes["ID"].Value);
			sb.Append("_AllChildren");
			string key = sb.ToString();
			return (currentSelect == null) ? "" : (currentSelect.ContainsKey(key) ? "checked" : "");
		}
		#endregion

		#region Internal Methods
		/// <summary>
		/// This method is ONLY invoked by the associated designer to show the control contents at design time.
		/// </summary>
		internal void Design()
		{
			EnsureChildControls();
		}
		#endregion
	}

	#region Class AdminOrgTreeDesigner
	/// <summary>
	/// Designer associated with the OrgTree control.
	/// </summary>
	/// <remarks>
	/// This class cannot be used directly.
	/// </remarks>
	public class AdminOrgTreeDesigner : ControlDesigner
	{
		#region Overrides
		/// <summary>
		/// Force the control to create its child controls for display at design time.
		/// </summary>
		/// <returns>HTML representation of the SelectTree control</returns>
		public override string GetDesignTimeHtml()
		{
			((AdminOrgTree)Component).Design();
			return base.GetDesignTimeHtml();
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
